package cn.ilikes.tools.nosql.jpa.clients.indexs.index.lucenes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TopScoreDocCollector;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.wltea.analyzer.lucene.IKAnalyzer;

import cn.ilikes.tools.logs.Loggerfactory;
import cn.ilikes.tools.nosql.jpa.JPASQLRunTimeException;
import cn.ilikes.tools.nosql.jpa.clients.indexs.ABClientIndex;
import cn.ilikes.tools.nosql.jpa.clients.indexs.ClientIndexingException;
import cn.ilikes.tools.nosql.jpa.clients.indexs.ClientIndexingRunTimeException;
import cn.ilikes.tools.nosql.jpa.clients.indexs.IClientIndex;
import cn.ilikes.tools.nosql.jpa.clients.indexs.index.ABIndexSource;
import cn.ilikes.tools.nosql.jpa.clients.indexs.index.ABLuceneIndexSource;
import cn.ilikes.tools.nosql.jpa.clients.indexs.units.LuceneEntityClassToDocument;
import cn.ilikes.tools.nosql.jpa.clients.indexs.units.LuceneEntityFieldsToDocuments;
import cn.ilikes.tools.nosql.jpa.clients.indexs.units.LuceneQueryUtils;
import cn.ilikes.tools.nosql.jpa.graphs.Node;
import cn.ilikes.tools.nosql.jpa.metadatas.EntityMetaData;
import cn.ilikes.tools.nosql.jpa.query.pages.PageEntitys;
import cn.ilikes.tools.nosql.jpa.unitls.PropertyAccessorHelper;

public abstract class LuceneClientIndex extends ABClientIndex {

	private Logger logger = Loggerfactory.instance(LuceneClientIndex.class);

	private LuceneEntityFieldsToDocuments luceneEntityFieldsToDocuments = new LuceneEntityFieldsToDocuments();

	private LuceneEntityClassToDocument luceneEntityClassToDocument = new LuceneEntityClassToDocument();
	
	public static ScoreDoc createScoreDoc(Properties properties) {
		String doc_ = properties.getProperty("lucene.doc");
		String score_ = properties.getProperty("lucene.score");
		String shardIndex_ = properties.getProperty("lucene.shardIndex");
		if (doc_ == null || score_ == null || shardIndex_ == null)
			return null;
		if (doc_.isEmpty() || score_.isEmpty() || shardIndex_.isEmpty())
			return null;
		int doc = Integer.parseInt(properties.getProperty("lucene.doc"));
		float score = Float.parseFloat(properties.getProperty("lucene.score"));
		int shardIndex = Integer.parseInt(properties.getProperty("lucene.shardIndex"));
		ScoreDoc scoreDoc = new ScoreDoc(doc, score, shardIndex);
		return scoreDoc;
	}

	public static Properties createProperties(ScoreDoc scoreDoc) {
		Properties properties = new Properties();
		properties.setProperty("lucene.doc", Integer.toString(scoreDoc.doc));
		properties.setProperty("lucene.score", Float.toString(scoreDoc.score));
		properties.setProperty("lucene.shardIndex", Integer.toString(scoreDoc.shardIndex));

		return properties;
	}
	
	protected LuceneClientIndex(ABIndexSource indexSource) {
		super(indexSource);

	}


	protected enum IndexAction {
		insert, update, remove
	}

	
	
	protected  abstract  IndexWriter createIndexWriter();
	
	protected  abstract  IndexReader createIndexReader();
	
	protected  abstract  IndexSearcher createIndexSearcher();

	@Override
	public void create(Node node) throws ClientIndexingException {
		if (exist(node))
			throw new ClientIndexingException("this node : " + PropertyAccessorHelper.getId(node.getEntity(), node.getEntityMetaData()) + " exist ! ");
		if (!Node.getEntity(node).getClass().equals(Node.getEntityMetaData(node).getEntityClazz()))
			throw new ClientIndexingRunTimeException("entity object class : " + Node.getEntity(node).getClass() + "  is not entityClazz :"
					+ Node.getEntityMetaData(node).getEntityClazz());
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, " index  @Entity[" + Node.getEntityMetaData(node).getEntityClazz().getSimpleName() + "] for entity :" + Node.getEntity(node));
		Document document = indexLuceneDocument(node);
		wirteDocument(Node.getEntityMetaData(node), document, Node.getEntity(node), IndexAction.insert);
	}
	
	private  Document indexLuceneDocument(Node node) throws ClientIndexingException {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "index document @Entity  [" + Node.getEntityMetaData(node).getEntityClazz().getName() + "] ");

		if (!Node.getEntityMetaData(node).getEntityIndex().isIndexable()) {
			throw new ClientIndexingException(Node.getEntityMetaData(node).getEntityClazz() + " can not index  set @index   true");
		}

		Document currentDoc = new Document();
		// Add entity class, PK info into document
		luceneEntityClassToDocument.addEntityClassToDocument(currentDoc, node);
		// Add all entity fields(columns) into document
		luceneEntityFieldsToDocuments.addEntityFieldsToDocumentst(currentDoc, node);
		return currentDoc;

	}
	
	
	protected abstract void  wirteDocument(EntityMetaData metadata, Document document, Object entity, IndexAction indexAction);



	

	@Override
	public boolean exist(EntityMetaData entityMetaData, Object key) throws ClientIndexingException {
		try {
			Term t = new Term(IClientIndex.JPA_SQL_ID_FIELD, LuceneQueryUtils.getJPAHBaseKeyofId(entityMetaData, key));
			Query query = new TermQuery(t);
			IndexSearcher indexSearcher = createIndexSearcher();
			if (indexSearcher == null) return false;
			TopDocs docs = indexSearcher.search(query, 1);
			int size = docs.scoreDocs.length;
			return size > 0;
		} catch (IOException e) {
			throw new ClientIndexingException(e);
		}
	}

	protected abstract void  removeDocument(Term term, IndexAction indexAction);
	
	@Override
	public void remove(Node node) throws ClientIndexingException {
		
			Object key = PropertyAccessorHelper.getId(node.getEntity(), node.getEntityMetaData());
			Term term = new Term(IClientIndex.JPA_SQL_ID_FIELD, LuceneQueryUtils.getJPAHBaseKeyofId(node.getEntityMetaData(), key));
			removeDocument(term,IndexAction.remove);

	}

	@Override
	public void remove(EntityMetaData entityMetaData, Object key) throws ClientIndexingException {

		          Term term = new Term(IClientIndex.JPA_SQL_ID_FIELD, LuceneQueryUtils.getJPAHBaseKeyofId(entityMetaData, key));

			removeDocument(term,IndexAction.remove);
	}

	@Override
	public boolean exist(Node node) throws ClientIndexingException {
		Object key = PropertyAccessorHelper.getId(node.getEntity(), node.getEntityMetaData());
		return exist(node.getEntityMetaData(), key);
	}

	@Override
	public long selectSizeByKeyId(Object keyId, EntityMetaData metadata) throws ClientIndexingException {
		try {
			IndexSearcher indexSearcher = createIndexSearcher();
			if (indexSearcher == null)
				return -1;
			String kunderaId = LuceneQueryUtils.toLowerCase(LuceneQueryUtils.getJPAHBaseKeyofId(metadata, keyId));
			Term term = new Term(IClientIndex.JPA_SQL_ID_FIELD, kunderaId);
			WildcardQuery query = new WildcardQuery(term);
			TotalHitCountCollector counter = new TotalHitCountCollector();
			indexSearcher.search(query, counter);
			int r = counter.getTotalHits();
			return r;
		} catch (IOException e) {
			throw new JPASQLRunTimeException(e);
		}

	}

	@Override
	public void update(Node node) throws ClientIndexingException {
		Document document = indexLuceneDocument(node);
		wirteDocument(Node.getEntityMetaData(node), document, Node.getEntity(node), IndexAction.update);
	}

	@Override
	public <V> List<V> search(String query, EntityMetaData metadata) throws ClientIndexingException {
		return searchResult(query, IClientIndex.INVALID, IClientIndex.INVALID, metadata, false);
	}

	public PageEntitys searchPage(String query, EntityMetaData metadata, Properties properties, int howMany) throws ClientIndexingException {
		PageEntitys pageEntitys = new PageEntitys();
		List indexKeyId = new ArrayList();
		IndexSearcher searcher = createIndexSearcher();
		QueryParser qp = new QueryParser(Version.LUCENE_41, IClientIndex.ENTITY_ID_FIELD, this.getAnalyzer());
		try {
			qp.setLowercaseExpandedTerms(false);
			Query q = qp.parse(query);
			ScoreDoc scoreDoc = LuceneClientIndex.createScoreDoc(properties);
			TopDocs docs = null;
			if (scoreDoc == null)
				docs = searcher.search(q, howMany);
			else
				docs = searcher.searchAfter(LuceneClientIndex.createScoreDoc(properties), q, howMany);
			ScoreDoc[] reult = docs.scoreDocs;
			int length = reult.length;
			if (Loggerfactory.isdebug(logger))
				Loggerfactory.debug(logger, "Searching  searchPage  query " + query + " ==>  " + length);
			ScoreDoc sc = null;
			for (int i = 0; i < length; i++) {
				sc = reult[i];
				Document doc = searcher.doc(sc.doc);
				indexKeyId.add(doc.get(IClientIndex.ENTITY_ID_FIELD));
			}
			pageEntitys.setProperties(LuceneClientIndex.createProperties(sc));
			pageEntitys.setEntitys(indexKeyId);
			return pageEntitys;
		} catch (ParseException e) {
			throw new ClientIndexingException("Error while parsing Lucene Query " + query, e);
		} catch (IOException e) {
			throw new ClientIndexingException(e);
		}
	}

	private Analyzer getAnalyzer() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <V> List<V> search(String query, EntityMetaData metadata, int firstResult, int howMany) throws ClientIndexingException {
		return searchResult(query, firstResult, howMany, metadata, false);

	}

	public <V> List<V> search(String fields, String value) throws ClientIndexingException {
		try {
			List<V> indexKeyId = new ArrayList<V>();
			Term term = new Term(fields, LuceneQueryUtils.toLowerCase(value));
			WildcardQuery query = new WildcardQuery(term);
			IndexSearcher indexSearcher =createIndexSearcher();
			TopDocs docs = indexSearcher.search(query, 100);
			for (ScoreDoc sc : docs.scoreDocs) {
				Document doc = indexSearcher.doc(sc.doc);
				@SuppressWarnings("unchecked")
				V entityId = (V) doc.get(IClientIndex.ENTITY_ID_FIELD);
				indexKeyId.add(entityId);
			}
			return indexKeyId;
		} catch (IOException e) {
			throw new ClientIndexingException(e);
		}

	}

	public long searchSize(String query) throws ClientIndexingException {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "search size with query[" + query + "]");
		IndexSearcher searcher = createIndexSearcher();
		QueryParser qp = new QueryParser(Version.LUCENE_41, IClientIndex.ENTITY_ID_FIELD, this.getAnalyzer());
		try {
			qp.setLowercaseExpandedTerms(false);
			Query q = qp.parse(query);
			TotalHitCountCollector counter = new TotalHitCountCollector();
			searcher.search(q, counter);
			int r = counter.getTotalHits();
			return r;
		} catch (ParseException e) {
			throw new ClientIndexingException("Error while parsing Lucene Query " + query, e);
		} catch (IOException e) {
			throw new ClientIndexingException(e);
		}

	}

	private <V> List<V> searchResult(String luceneQuery, int start, int count, EntityMetaData metadatas, boolean fetchRelation) throws ClientIndexingException {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "Searching index with query[" + luceneQuery + "], start:" + start + ", count:" + count + "  fetchRelation : " + fetchRelation);
		return searchStart(luceneQuery, start, count, metadatas, fetchRelation);

	}

	private <V> List<V> searchStart(String luceneQuery, int start, int count, EntityMetaData metadatas, boolean fetchRelation) throws ClientIndexingException {
		if (Loggerfactory.isdebug(logger))
			Loggerfactory.debug(logger, "Searching  START  COUNT index with query[" + luceneQuery + "], start:" + start + ", count:" + count + "  fetchRelation : " + fetchRelation);
		List<V> indexKeyId = new ArrayList<V>();
		IndexSearcher searcher = createIndexSearcher();
		QueryParser qp = new QueryParser(Version.LUCENE_41, IClientIndex.ENTITY_ID_FIELD, this.getAnalyzer());
		try {
			qp.setLowercaseExpandedTerms(false);
			Query q = qp.parse(luceneQuery);
			int selectCount = count;
			if (selectCount == IClientIndex.INVALID) {
				TotalHitCountCollector counter = new TotalHitCountCollector();
				searcher.search(q, counter);
				int r = counter.getTotalHits();
				if (r == 0)
					return indexKeyId;
				selectCount = r;
			}
			if (start == IClientIndex.INVALID)
				start = 0;
			TopScoreDocCollector collector = TopScoreDocCollector.create(start + selectCount, false);
			searcher.search(q, collector);
			TopDocs docs = collector.topDocs(start, selectCount);
			for (ScoreDoc sc : docs.scoreDocs) {

				Document doc = searcher.doc(sc.doc);
				@SuppressWarnings("unchecked")
				V entityId = (V) doc.get(IClientIndex.ENTITY_ID_FIELD);
				indexKeyId.add(entityId);
			}
			return indexKeyId;
		} catch (ParseException e) {
			throw new ClientIndexingException("Error while parsing Lucene Query " + luceneQuery, e);
		} catch (IOException e) {
			throw new ClientIndexingException(e);
		}

	}

	
	protected  abstract Analyzer createAnalyzer();

}
